package io.github.loserya.core.sdk.aggregate;

import io.github.loserya.core.sdk.aggregate.entity.AggregationField;
import io.github.loserya.core.sdk.aggregate.entity.CustomGroupOperation;
import io.github.loserya.core.sdk.aggregate.entity.MogoField;
import io.github.loserya.core.sdk.aggregate.entity.ParsedQuery;
import io.github.loserya.utils.ExceptionUtils;
import io.github.loserya.utils.QueryBuildUtils;
import org.bson.Document;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;

/**
 * 参数构建者
 *
 * @author loser
 * @date 2024/7/6
 */
public class AggregationBuilder {

    private LambdaAggregate lambdaAggregate;

    private AggregationBuilder() {
    }

    public static AggregationBuilder builder() {
        return new AggregationBuilder();
    }

    public <T, R> AggregationBuilder aggregate(LambdaAggregate<T, R> lambdaAggregate) {
        this.lambdaAggregate = lambdaAggregate;
        return this;
    }

    public <T, R> Aggregation build() {

        // 00 必要参数校验
        check();

        ParsedQuery<T, R> parsedQuery = lambdaAggregate.getParsedQuery();
        // 01 构建 match 条件
        List<AggregationOperation> operations = new ArrayList<>();
        if (Objects.nonNull(parsedQuery.getWhereClause())) {
            operations.add(context -> new Document("$match", parsedQuery.getWhereClause().getQueryObject()));
        }

        // 02 构建 分组 条件
        Document group = null;
        if (parsedQuery.getGroupByField().size() > 0) {
            group = new Document();
            for (String filed : parsedQuery.getGroupByField()) {
                group.put(filed, "$" + filed);
            }
        }
        Document groupFields = new Document("_id", group);

        // 03 构建 聚合 函数
        for (AggregationField aggregationField : parsedQuery.getAggregations()) {
            String field = "$" + aggregationField.getField();
            String alias = aggregationField.getAlias();
            switch (aggregationField.getFunction()) {
                case SUM:
                    Document sumConvertToDecimal = new Document("$convert",
                            new Document("input", field)
                                    .append("to", "decimal")
                                    .append("onError", 0)
                                    .append("onNull", 0));
                    groupFields.append(alias, new Document("$sum", sumConvertToDecimal));
                    break;
                case AVG:
                    Document avgConvertToDecimal = new Document("$convert",
                            new Document("input", field)
                                    .append("to", "decimal")
                                    .append("onError", 0)
                                    .append("onNull", 0));
                    groupFields.append(alias, new Document("$avg", avgConvertToDecimal));
                    break;
                case COUNT:
                    groupFields.append(alias, new Document("$sum", 1));
                    break;
                case MIN:
                    groupFields.append(alias, new Document("$min", field));
                    break;
                case MAX:
                    groupFields.append(alias, new Document("$max", field));
                    break;
                default:
                    throw ExceptionUtils.mpe("Unsupported aggregation function: " + aggregationField.getFunction());
            }
        }

        // 04 构建 字段 投影
        if (parsedQuery.getFields().size() > 0) {
            Document document = new Document();
            for (MogoField field : parsedQuery.getFields()) {
                document.put(field.getAlias(), "$" + field.getField());
            }
            groupFields.append("data", new Document("$push", document));
        }
        operations.add(new CustomGroupOperation(groupFields));
        operations.add(context -> new Document("$project", convertProject(lambdaAggregate)));

        // 05 构建排序
        if (parsedQuery.getSortConditions().size() > 0) {
            List<Sort.Order> orders = QueryBuildUtils.buildSort(parsedQuery.getSortConditions());
            operations.add(Aggregation.sort(Sort.by(orders)));
        }

        // 06 构建分页
        if (parsedQuery.getSkip() > 0) {
            operations.add(Aggregation.skip(parsedQuery.getSkip()));
        }
        if (parsedQuery.getLimit() > 0) {
            operations.add(Aggregation.limit(parsedQuery.getLimit()));
        }
        return newAggregation(operations);

    }

    private void check() {

        if (Objects.isNull(this.lambdaAggregate)) {
            throw ExceptionUtils.mpe("lambdaAggregate is null");
        }

        ParsedQuery parsedQuery = this.lambdaAggregate.getParsedQuery();
        if (Objects.isNull(parsedQuery.getClazz()) && Objects.isNull(parsedQuery.getCollectionName())) {
            throw ExceptionUtils.mpe("lambdaAggregate collection class is null , please use LambdaAggregate.from() to set collection class");
        }

    }

    private <R, T> Document convertProject(LambdaAggregate<T, R> lambdaAggregate) {

        Document document = new Document();
        document.put("_id", 1);
        ParsedQuery<T, R> parsedQuery = lambdaAggregate.getParsedQuery();
        for (AggregationField aggregation : parsedQuery.getAggregations()) {
            document.put(aggregation.getAlias(), 1);
        }
        // 只返回数组元素中的第一个
        for (MogoField field : parsedQuery.getFields()) {
            document.put(field.getAlias(), new Document("$arrayElemAt", Arrays.asList("$data." + field.getAlias(), 0)));
        }
        return document;

    }

}
